/*
 * Copyright (C) 2009 Rob Cash
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * SubscriptionServiceTransformer.java
 * 
 * Created on Jan 17, 2009 at 12:52:30 PM
 */

package org.robcash.apps.messagepopup.dataaccess.util;

import static org.robcash.apps.messagepopup.BeanNameConstants.SCHEDULE_BEAN_NAME;
import static org.robcash.apps.messagepopup.BeanNameConstants.SUBSCRIPTION_BEAN_NAME;
import static org.robcash.apps.messagepopup.BeanNameConstants.SUBSCRIPTION_STORE_BEAN_NAME;

import java.io.File;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.robcash.apps.messagepopup.MessageException;
import org.robcash.apps.messagepopup.vo.MessageStore;
import org.robcash.apps.messagepopup.vo.MessageStoreFactory;
import org.robcash.apps.messagepopup.vo.MessageStoreLocation;
import org.robcash.apps.messagepopup.vo.Schedule;
import org.robcash.apps.messagepopup.vo.Subscription;
import org.robcash.apps.messagepopup.vo.SubscriptionStore;
import org.robcash.apps.messagepopup.vo.SubscriptionStoreFactory;
import org.robcash.apps.messagepopup.vo.impl.LocalFileMessageStoreLocation;
import org.robcash.apps.messagepopup.vo.impl.RemoteFileMessageStoreLocation;
import org.robcash.xmlns.apps._2009._01.MessagePopup.LocalFileMessageStoreLocationType;
import org.robcash.xmlns.apps._2009._01.MessagePopup.MessageStoreLocationType;
import org.robcash.xmlns.apps._2009._01.MessagePopup.ObjectFactory;
import org.robcash.xmlns.apps._2009._01.MessagePopup.RemoteFileMessageStoreLocationType;
import org.robcash.xmlns.apps._2009._01.MessagePopup.ScheduleType;
import org.robcash.xmlns.apps._2009._01.MessagePopup.SubscriptionStoreType;
import org.robcash.xmlns.apps._2009._01.MessagePopup.SubscriptionType;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;

/**
 * Transformer for subscription service data and value objects
 * @author Rob
 */
public class SubscriptionServiceTransformer implements BeanFactoryAware,
        Transformer<SubscriptionStoreType, SubscriptionStore> {

    /**
     * Spring bean factory
     */
    private BeanFactory beanFactory;

    /**
     * Subscription store factory
     */
    private SubscriptionStoreFactory storeFactory;

    /**
     * XML object factory
     */
    private ObjectFactory xmlObjectFactory;

    /**
     * Create a new subscription service transformer
     */
    public SubscriptionServiceTransformer() {
        this.xmlObjectFactory = new ObjectFactory();
    }

    /**
     * Set the Spring bean factory
     * @param factory Spring bean factory that created this instance
     */
    public void setBeanFactory(BeanFactory factory) {
        this.beanFactory = factory;
    }

    /**
     * Set the subscription store factory
     * @param factory Factory used to create SubscriptionStore objects
     */
    public void setSubscriptionStoreFactory(SubscriptionStoreFactory factory) {
        this.storeFactory = factory;
    }

    /**
     * Convert to a data object
     * @param valueObject Value object that should be converted to a data object
     * @return Data object
     * @throws MessageException Thrown when the value object cannot be
     * transformed to a data object
     */
    public SubscriptionStoreType toDataObject(SubscriptionStore valueObject)
            throws MessageException {

        // Create factory that will hold result of transformation
        SubscriptionStoreType store =
                xmlObjectFactory.createSubscriptionStoreType();

        // First, convert schedules
        Map<String, ScheduleType> xmlSchedules = new HashMap<String, ScheduleType>();
        for( Schedule aSchedule : valueObject.getSchedules() ) {
            ScheduleType xmlSchedule = convertSchedule(aSchedule);
            xmlSchedules.put(xmlSchedule.getName(), xmlSchedule);
            store.getSchedule().add(xmlSchedule);
        }

        // Next, convert subscriptions
        for( Subscription aSubscription : valueObject.getSubscriptions() ) {
            store.getSubscription().add(convertSubscription(aSubscription));
        }

        return store;
    }

    /**
     * Convert a schedule from a value object to an XML object
     * @param schedule Schedule to convert
     * @return XML schedule
     */
    protected ScheduleType convertSchedule(Schedule schedule) {
        ScheduleType xmlSchedule = xmlObjectFactory.createScheduleType();
        xmlSchedule.setName(schedule.getName());
        xmlSchedule.setStartTime(schedule.getStartTime());
        xmlSchedule.setEndTime(schedule.getEndTime());
        xmlSchedule.setFrequency(BigInteger.valueOf(schedule.getFrequency()));
        xmlSchedule.setPeriod(BigInteger.valueOf(schedule.getPeriod()));
        ScheduleType.Days days = new ScheduleType.Days();
        for( Integer aDay : schedule.getDays() ) {
            if ( aDay.equals(Schedule.SUNDAY) ) {
                days.setSunday(true);
            }
            else if ( aDay.equals(Schedule.MONDAY) ) {
                days.setMonday(true);
            }
            else if ( aDay.equals(Schedule.TUESDAY) ) {
                days.setTuesday(true);
            }
            else if ( aDay.equals(Schedule.WEDNESDAY) ) {
                days.setWednesday(true);
            }
            else if ( aDay.equals(Schedule.THURSDAY) ) {
                days.setThursday(true);
            }
            else if ( aDay.equals(Schedule.FRIDAY) ) {
                days.setFriday(true);
            }
            else if ( aDay.equals(Schedule.SATURDAY) ) {
                days.setSaturday(true);
            }
        }
        xmlSchedule.setDays(days);

        return xmlSchedule;
    }

    /**
     * Convert subscription from an XML object to a value object
     * @param aSubscription Subscription from MessageStore XML
     * @return Subscription value object
     * @throws MessageException thrown if the subscription cannot be converted
     * from a value object to a data object
     */
    protected SubscriptionType convertSubscription(Subscription aSubscription)
            throws MessageException {
        SubscriptionType xmlSubscription =
                xmlObjectFactory.createSubscriptionType();
        xmlSubscription.setName(aSubscription.getName());
        xmlSubscription.setSchedule(aSubscription.getSchedule().getName());
        xmlSubscription.setMessageStoreLocation(convertLocation(
                aSubscription.getMessageStore().getLocation()));

        return xmlSubscription;
    }

    /**
     * Convert an unknown or unsupported message store location to a data
     * object. This method always throws an exception
     * @param aLocation Message store loction to convert
     * @return None - this method always throws an exception
     * @throws MessageException Always thrown
     */
    protected MessageStoreLocationType convertLocation(
            MessageStoreLocation aLocation) throws MessageException {
        if ( aLocation instanceof LocalFileMessageStoreLocation ) {
            LocalFileMessageStoreLocationType location =
                    xmlObjectFactory.createLocalFileMessageStoreLocationType();
            location.setLocalFile(((LocalFileMessageStoreLocation)aLocation).getLocalFile().getAbsolutePath());
            return location;
        }
        else if ( aLocation instanceof RemoteFileMessageStoreLocation ) {
            RemoteFileMessageStoreLocationType location =
                    xmlObjectFactory.createRemoteFileMessageStoreLocationType();
            location.setRemoteFile(((RemoteFileMessageStoreLocation)aLocation).getRemoteFile().toString());
            return location;
        }
        throw new MessageException("Unknown or unsupported message store location type");
    }

    /**
     * Convert to a value object
     * @param dataObject Data object that should be converted to a value object
     * @return Value object
     * @throws MessageException Thrown when the data object cannot be
     * transformed to a value object
     */
    public SubscriptionStore toValueObject(SubscriptionStoreType dataObject)
            throws MessageException {

        // Subscription store being created/returned
        SubscriptionStore store = (SubscriptionStore) beanFactory.getBean(
                SUBSCRIPTION_STORE_BEAN_NAME);
        store.clear();

        // Get and convert schedules
        Map<String, Schedule> scheduleMap = new HashMap<String, Schedule>();
        for( ScheduleType storeSchedule : dataObject.getSchedule() )
        {
            Schedule schedule = convertSchedule(storeSchedule);
            scheduleMap.put(schedule.getName(), schedule);
            store.add(schedule);
        }

        // Get and convert subscriptions
        for( SubscriptionType storeSubscription : dataObject.getSubscription() )
        {
            Subscription subscription = convertSubscription(storeSubscription, scheduleMap);
            store.add(subscription);
        }

        return store;
    }

    /**
     * Convert schedule from an XML object to a value object
     * @param aSchedule Schedule from MessageStore XML
     * @return Schedule value object
     */
    protected Schedule convertSchedule(ScheduleType aSchedule) {
        Schedule schedule = (Schedule) beanFactory.getBean(SCHEDULE_BEAN_NAME);
        schedule.setName(aSchedule.getName());
        schedule.setStartTime(aSchedule.getStartTime());
        schedule.setEndTime(aSchedule.getEndTime());
        schedule.setFrequency(aSchedule.getFrequency().intValue());
        schedule.setPeriod(aSchedule.getPeriod().intValue());
        Set<Integer> days = new HashSet<Integer>();
        if ( aSchedule.getDays().isSunday() == Boolean.TRUE ) {
            days.add(Schedule.SUNDAY);
        }
        if ( aSchedule.getDays().isMonday() == Boolean.TRUE ) {
            days.add(Schedule.MONDAY);
        }
        if ( aSchedule.getDays().isTuesday() == Boolean.TRUE ) {
            days.add(Schedule.TUESDAY);
        }
        if ( aSchedule.getDays().isWednesday() == Boolean.TRUE ) {
            days.add(Schedule.WEDNESDAY);
        }
        if ( aSchedule.getDays().isThursday() == Boolean.TRUE ) {
            days.add(Schedule.THURSDAY);
        }
        if ( aSchedule.getDays().isFriday() == Boolean.TRUE ) {
            days.add(Schedule.FRIDAY);
        }
        if ( aSchedule.getDays().isSaturday() == Boolean.TRUE ) {
            days.add(Schedule.SATURDAY);
        }
        schedule.setDays(days);
        return schedule;
    }

    /**
     * Convert subscription from an XML object to a value object
     * @param aSubscription Subscription from MessageStore XML
     * @param scheduleMap Map of schedule names to schedule value objects
     * @return Subscription value object
     * @throws MessageException Thrown if the subscription cannot be converted
     * from a data object to a value object
     */
    protected Subscription convertSubscription(SubscriptionType aSubscription,
                Map<String, Schedule> scheduleMap) throws MessageException {
        Subscription subscription = (Subscription) beanFactory.getBean(
                SUBSCRIPTION_BEAN_NAME);
        subscription.setName(aSubscription.getName());
        subscription.setSchedule(scheduleMap.get(aSubscription.getSchedule()));
        MessageStore store = MessageStoreFactory.newInstance().newMessageStore(
                convertLocation(aSubscription.getMessageStoreLocation()));
        subscription.setMessageStore(store);

        return subscription;
    }

    /**
     * Convert an unknown XML message store location to a value object.
     * This method will always throw a MessageException
     * @param aLocation Message store location from XML
     * @return None - this method always throws an exception
     * @throws MessageException Always thrown as XML message store location
     * type is unknown or unsupported
     */
    protected MessageStoreLocation convertLocation(
            MessageStoreLocationType aLocation) throws MessageException {
        if ( aLocation instanceof LocalFileMessageStoreLocationType ) {
            return new LocalFileMessageStoreLocation(new File(
                    ((LocalFileMessageStoreLocationType)aLocation)
                    .getLocalFile()));
        }
        else if ( aLocation instanceof RemoteFileMessageStoreLocationType ) {
            try {
                return new RemoteFileMessageStoreLocation(new URL(
                        ((RemoteFileMessageStoreLocationType)aLocation)
                        .getRemoteFile()));
            }
            catch(MalformedURLException e) {
                throw new MessageException("Location of remote message store is " +
                        "not valid", e);
            }
        }
        throw new MessageException("Unknown or unsupported message store location type");
    }

}
